(ns metabase.server.middleware.auth
  "Middleware related to enforcing authentication/API keys (when applicable). Unlike most other middleware most of this
  is not used as part of the normal `app`; it is instead added selectively to appropriate routes."
  (:require
   [clojure.string :as str]
   [metabase.models.setting :refer [defsetting]]
   [metabase.server.request.util :as req.util]
   [metabase.util.i18n :refer [deferred-trs]]))

(def ^:private ^:const ^String static-metabase-api-key-header "x-metabase-apikey")

(defn enforce-authentication
  "Middleware that returns a 401 response if `request` has no associated `:metabase-user-id`."
  [handler]
  (with-meta
   (fn [{:keys [metabase-user-id] :as request} respond raise]
     (if metabase-user-id
       (handler request respond raise)
       (respond req.util/response-unauthentic)))
   (meta handler)))

(defn- wrap-static-api-key* [{:keys [headers], :as request}]
  (if-let [api-key (headers static-metabase-api-key-header)]
    (assoc request :static-metabase-api-key api-key)
    request))

(defn wrap-static-api-key
  "Middleware that sets the `:static-metabase-api-key` keyword on the request if a valid API Key can be found. We check
  the request headers for `X-METABASE-APIKEY` and if it's not found then no keyword is bound to the request."
  [handler]
  (fn [request respond raise]
    (handler (wrap-static-api-key* request) respond raise)))

(defsetting api-key
  "When set, this API key is required for all API requests."
  :visibility :internal
  :doc "Middleware that enforces validation of the client via the request header X-Metabase-Apikey.
        If the header is available, then it’s validated against MB_API_KEY.
        When it matches, the request continues; otherwise it’s blocked with a 403 Forbidden response.")

(defn static-api-key
  "We don't want to change the name of the setting from `MB_API_KEY`, but we want to differentiate this static key from
  the API keys that can be generated by admins."
  [] (api-key))

(def mb-api-key-doc-url
  "Url for documentation on how to set MB_API_KEY."
  "https://www.metabase.com/docs/latest/configuring-metabase/environment-variables#mb_api_key")

(def key-not-set-response
  "Response when the MB_API_KEY is not set."
  {:status 403
   :body (deferred-trs "MB_API_KEY is not set. See {0} for details" mb-api-key-doc-url)})

(defn enforce-static-api-key
  "Middleware that enforces validation of the client via API Key, canceling the request processing if the check fails.

  Validation is handled by first checking for the presence of the `:static-metabase-api-key` on the request. If the
  api key is available then we validate it by checking it against the configured `:mb-api-key` value set in our global
  config.

  If the request `:static-metabase-api-key` matches the configured `api-key` value then the request continues,
  otherwise we reject the request and return a 403 Forbidden response.

  This variable only works for /api/notify/db/:id endpoint"
  [handler]
  (fn [{:keys [static-metabase-api-key], :as request} respond raise]
    (cond (str/blank? (static-api-key))
          (respond key-not-set-response)

          (not static-metabase-api-key)
          (respond req.util/response-forbidden)

          (= (static-api-key) static-metabase-api-key)
          (handler request respond raise)

          :else
          (respond req.util/response-forbidden))))
